package dslab.core;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * interface specifying what a DMAP implementation should offer
 */
public interface DMTPProtocol extends Protocol {
    /*
     * handler functions each pertaining to a respective instruction of
     * the DMTP protocol; refer to the DMTP specification to find out
     * more about the individual directives
     */

    /**
     * handles the 'begin' directive
     * @param out writer bound to client connection output stream
     * @throws IOException thrown when a write operation fails
     */
    void handleBegin(Writer out) throws IOException;

    /**
     * handles the 'to' directive
     * @param out writer bound to client connection output stream
     * @param addresses addresses in the format of `user@mailbox`, assumed to be
     *                  already validated
     * @throws IOException thrown when a write operation fails
     */
    void handleTo(Writer out, List<String> addresses, String addressString) throws IOException;

    /**
     * handles the 'from' directive
     * @param out writer bound to client connection output stream
     * @param address address in the format of `user@mailbox`, assumed to be
     *                  already validated
     * @throws IOException thrown when a write operation fails
     */
    void handleFrom(Writer out, String address) throws IOException;

    /**
     * handles the 'subject' directive
     * @param out writer bound to client connection output stream
     * @param subject string containing the subject, freeform
     * @throws IOException thrown when a write operation fails
     */
    void handleSubject(Writer out, String subject) throws IOException;

    /**
     * handles the 'data' directive
     * @param out writer bound to client connection output stream
     * @param data string containing the data, freeform
     * @throws IOException thrown when a write operation fails
     */
    void handleData(Writer out, String data) throws IOException;

    /**
     * handles the 'send' directive
     * @param out writer bound to client connection output stream
     * @throws IOException thrown when a write operation fails
     */
    void handleSend(Writer out) throws IOException, InterruptedException;

    /**
     * handles the 'quit' directive
     * @param out writer bound to client connection output stream
     * @throws IOException thrown when a write operation fails
     */
    void handleQuit(Writer out) throws IOException;

    /**
     * general request handling function that multiplexes a request to its
     * associated handler methods
     * @param out Writer of the output stream of the client connection
     * @param request request string sent by the client
     * @throws IOException in case of a broken connection or writer an exception is thrown
     */
    default void processRequest(Writer out, String request) throws IOException, InterruptedException {
        String header;
        String body;

        // check if IO is available
        if (out == null)
            throw new IOException();

        if (request.contains(" ")) {
            // split multi word instructions
            var headerAndBody = request.split(" ", 2);
            assert headerAndBody.length == 2;
            header = headerAndBody[0];
            body = headerAndBody[1];
        } else {
            // single word instructions have no body
            header = request;
            body = null;
        }

        // calls respective handlers and optionally does validation (to fail early)
        switch (header) {
            case "begin":
                handleBegin(out);
                return;
            case "to":
                // can't be null
                if (body == null) {
                    raiseWarning(out, String.format("error illegal instruction body%n"));
                    return;
                }

                // extract individual addresses first
                List<String> addresses = new ArrayList<>();
                if (body.contains(","))
                    Collections.addAll(addresses, body.split(","));
                else
                    addresses.add(body);

                // validate format
                for (String address : addresses) {
                    if (isInvalidRFC822Address(address)) {
                        // exit preemptively in failure case
                        raiseWarning(out, String.format("error illegal instruction body%n"));
                        return;
                    }
                }

                handleTo(out, addresses, body);
                return;
            case "from":
                // validate body before calling handler
                if (body == null || isInvalidRFC822Address(body))
                    raiseWarning(out, String.format("error illegal instruction body%n"));
                else
                    handleFrom(out, body);
                return;
            case "subject":
                handleSubject(out, body);
                return;
            case "data":
                handleData(out, body);
                return;
            case "send":
                handleSend(out);
                return;
            case "quit":
                handleQuit(out);
                return;
            default:
                raiseError(out, String.format("error protocol error%n"));
        }
    }

    /**
     * validates whether an address is RFC822 compliant
     * @param address address to validate
     * @return whether address is valid or not
     */
    default boolean isInvalidRFC822Address(String address) {
        // TODO: develop more sophisticated email address verification
        return !address.contains("@") || address.contains(" ");
    }
}
